// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/p2p/ipc_network_manager.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/sys_byteorder.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/public/common/content_switches.h"
#include "jingle/glue/utils.h"
#include "net/base/ip_address.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_interfaces.h"
#include "third_party/webrtc/base/socketaddress.h"
#include <string>

namespace content {

namespace {

    rtc::AdapterType ConvertConnectionTypeToAdapterType(
        net::NetworkChangeNotifier::ConnectionType type)
    {
        switch (type) {
        case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
            return rtc::ADAPTER_TYPE_UNKNOWN;
        case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
            return rtc::ADAPTER_TYPE_ETHERNET;
        case net::NetworkChangeNotifier::CONNECTION_WIFI:
            return rtc::ADAPTER_TYPE_WIFI;
        case net::NetworkChangeNotifier::CONNECTION_2G:
        case net::NetworkChangeNotifier::CONNECTION_3G:
        case net::NetworkChangeNotifier::CONNECTION_4G:
            return rtc::ADAPTER_TYPE_CELLULAR;
        default:
            return rtc::ADAPTER_TYPE_UNKNOWN;
        }
    }

} // namespace

IpcNetworkManager::IpcNetworkManager(NetworkListManager* network_list_manager)
    : network_list_manager_(network_list_manager)
    , start_count_(0)
    , network_list_received_(false)
    , weak_factory_(this)
{
    network_list_manager_->AddNetworkListObserver(this);
}

IpcNetworkManager::~IpcNetworkManager()
{
    DCHECK(!start_count_);
    network_list_manager_->RemoveNetworkListObserver(this);
}

void IpcNetworkManager::StartUpdating()
{
    if (network_list_received_) {
        // Post a task to avoid reentrancy.
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&IpcNetworkManager::SendNetworksChangedSignal, weak_factory_.GetWeakPtr()));
    }
    ++start_count_;
}

void IpcNetworkManager::StopUpdating()
{
    DCHECK_GT(start_count_, 0);
    --start_count_;
}

void IpcNetworkManager::OnNetworkListChanged(
    const net::NetworkInterfaceList& list,
    const net::IPAddress& default_ipv4_local_address,
    const net::IPAddress& default_ipv6_local_address)
{
    // Update flag if network list received for the first time.
    if (!network_list_received_)
        network_list_received_ = true;

    // Default addresses should be set only when they are in the filtered list of
    // network addresses.
    bool use_default_ipv4_address = false;
    bool use_default_ipv6_address = false;

    // rtc::Network uses these prefix_length to compare network
    // interfaces discovered.
    std::vector<rtc::Network*> networks;
    for (net::NetworkInterfaceList::const_iterator it = list.begin();
         it != list.end(); it++) {
        rtc::IPAddress ip_address = jingle_glue::NetIPAddressToRtcIPAddress(it->address);
        DCHECK(!ip_address.IsNil());

        rtc::IPAddress prefix = rtc::TruncateIP(ip_address, it->prefix_length);
        std::unique_ptr<rtc::Network> network(
            new rtc::Network(it->name, it->name, prefix, it->prefix_length,
                ConvertConnectionTypeToAdapterType(it->type)));
        network->set_default_local_address_provider(this);

        rtc::InterfaceAddress iface_addr;
        if (it->address.IsIPv4()) {
            use_default_ipv4_address |= (default_ipv4_local_address == it->address);
            iface_addr = rtc::InterfaceAddress(ip_address);
        } else {
            DCHECK(it->address.IsIPv6());
            iface_addr = rtc::InterfaceAddress(ip_address, it->ip_address_attributes);

            // Only allow non-private, non-deprecated IPv6 addresses which don't
            // contain MAC.
            if (rtc::IPIsMacBased(iface_addr) || (it->ip_address_attributes & net::IP_ADDRESS_ATTRIBUTE_DEPRECATED) || rtc::IPIsPrivate(iface_addr)) {
                continue;
            }

            use_default_ipv6_address |= (default_ipv6_local_address == it->address);
        }
        network->AddIP(iface_addr);
        networks.push_back(network.release());
    }

    // Update the default local addresses.
    rtc::IPAddress ipv4_default;
    rtc::IPAddress ipv6_default;
    if (use_default_ipv4_address) {
        ipv4_default = jingle_glue::NetIPAddressToRtcIPAddress(default_ipv4_local_address);
    }
    if (use_default_ipv6_address) {
        ipv6_default = jingle_glue::NetIPAddressToRtcIPAddress(default_ipv6_local_address);
    }
    set_default_local_addresses(ipv4_default, ipv6_default);

    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kAllowLoopbackInPeerConnection)) {
        std::string name_v4("loopback_ipv4");
        rtc::IPAddress ip_address_v4(INADDR_LOOPBACK);
        rtc::Network* network_v4 = new rtc::Network(
            name_v4, name_v4, ip_address_v4, 32, rtc::ADAPTER_TYPE_UNKNOWN);
        network_v4->set_default_local_address_provider(this);
        network_v4->AddIP(ip_address_v4);
        networks.push_back(network_v4);

        rtc::IPAddress ipv6_default_address;
        // Only add IPv6 loopback if we can get default local address for IPv6. If
        // we can't, it means that we don't have IPv6 enabled on this machine and
        // bind() to the IPv6 loopback address will fail.
        if (GetDefaultLocalAddress(AF_INET6, &ipv6_default_address)) {
            DCHECK(!ipv6_default_address.IsNil());
            std::string name_v6("loopback_ipv6");
            rtc::IPAddress ip_address_v6(in6addr_loopback);
            rtc::Network* network_v6 = new rtc::Network(
                name_v6, name_v6, ip_address_v6, 64, rtc::ADAPTER_TYPE_UNKNOWN);
            network_v6->set_default_local_address_provider(this);
            network_v6->AddIP(ip_address_v6);
            networks.push_back(network_v6);
        }
    }

    bool changed = false;
    NetworkManager::Stats stats;
    MergeNetworkList(networks, &changed, &stats);
    if (changed)
        SignalNetworksChanged();

    // Send interface counts to UMA.
    UMA_HISTOGRAM_COUNTS_100("WebRTC.PeerConnection.IPv4Interfaces",
        stats.ipv4_network_count);
    UMA_HISTOGRAM_COUNTS_100("WebRTC.PeerConnection.IPv6Interfaces",
        stats.ipv6_network_count);
}

void IpcNetworkManager::SendNetworksChangedSignal()
{
    SignalNetworksChanged();
}

} // namespace content
